/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File SqlNodeModification.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.data.UpdateType;

import java.io.Serializable;
import java.sql.SQLException;

public class SqlNodeModification {
	SqlNodeModification parentModification;

	final SqlNodeBase node;

	UpdateType updateType;

	Object[] oldValues, newValues;

	SqlNodesetModification[] nodesets;

	final SqlManipulator man;

	@SuppressWarnings("unchecked")
	public SqlNodeModification(SqlNodeBase node, SqlManipulator man) {
		this.node = node;
		this.man = man;
		this.updateType = UpdateType.UNCHANGED;

		SqlMapping mapping = node.mapping;

		NodeType nodeType = mapping.nodeType;

		oldValues = newValues = null; // delay populating (be lazy)

		// create array, but do not initialize elements (be lazy)
		nodesets = new SqlNodesetModification[nodeType.nodesetsNumber()];
	}

	SqlNodesetModification getSubnodes(int index)
			throws OperationFailedException {
		SqlNodesetModification m = nodesets[index];

		if (m == null) {
			m = new SqlNodesetModification(node, man, index);
			nodesets[index] = m;
		}

		return m;
	}

	SqlNodeModification parentModification() {
		if (parentModification == null) {
			SqlNodeBase par = node.getParent();
			if (par == null)
				return null;

			parentModification = man.modification(par);
		}

		return parentModification;
	}

	void populateValues() throws OperationFailedException {
		if (oldValues != null)
			return;

		SqlMapping mapping = node.mapping;
		oldValues = mapping.getValues(node);
		newValues = oldValues.clone();
	}

	public SqlNodeModification createSubnode(int index) throws SQLException,
			OperationFailedException {
		SqlInnerNode n = man.addNode(node, index);

		SqlNodeModification m2 = new SqlNodeModification(n, man);

		m2.setUpdateType(UpdateType.ADDED);

		return m2;
	}

	public void removeSubnode(int index, SqlNodeModification m2)
			throws OperationFailedException {
		if (m2.updateType == UpdateType.REMOVED)
			return; // TODO exception?

		m2.setUpdateType(UpdateType.REMOVED);
	}

	public void removeSubnode() throws OperationFailedException {
		if (updateType == UpdateType.REMOVED)
			return; // TODO exception?

		setUpdateType(UpdateType.REMOVED);
	}

	public void setValue(int index, Serializable value)
			throws OperationFailedException {
		populateValues();

		newValues[index] = value;

		setUpdateType(UpdateType.CHANGED);
	}

	void setUpdateType(UpdateType u) throws OperationFailedException {
		assert u != UpdateType.UNCHANGED;
		assert updateType != UpdateType.REMOVED;

		switch (updateType) {
		case ADDED:
			if (u != UpdateType.REMOVED)
				return;
			break;
		case CHANGED:
			assert u != UpdateType.ADDED;
		}

		SqlNodeModification parentMod = parentModification();

		if (parentMod != null) {
			SqlNodesetModification s;
			int parentIndex = ((SqlInnerMapping) node.mapping).parentIndex;
			s = parentMod.getSubnodes(parentIndex);

			s.moveTo(this, u);
		}

		updateType = u;
	}

	public void applyChanges() throws Exception {
		SqlMapping mapping = node.mapping;

		switch (updateType) {
		case ADDED:
			if (newValues != null)
				mapping.write(node, oldValues, newValues);
			mapping.makeVisible(node);
			break;

		case REMOVED:
			((SqlInnerMapping) mapping).delete((SqlInnerNode) node);
			break;

		case CHANGED:
			if (newValues != null)
				mapping.write(node, oldValues, newValues);
			break;

		default:
			assert false;

		}

		mapping.commit();
	}
}
